<?php
/**
 * Classe Recherche de l'univers 1. Les constructions ne peuvent être arrêter. Une seul instance ne peut être créer par joueur. Il faut sélectionner la planète où les action seront effectué au risque d'avoir des incohérence.
 * @attention Les observateurs peuvent utilisés l'id de la planète, notament quand un bâtiment ce termine. Il ne faut pas oublier de séléctionner l'id de la planète de la recherche l'ors de la modification des bâtiments. Normalement ceci ce fait automatiquement lors de l'utilisation de U1_Planet::getInstance()
 *
 * @author jonathan <jo.link.noir@gmail.com>
 * @version 1.3: jo_link_noir
 * @date 06-07-2010
 */
class U1_Research extends SW_Structure_Observer_Single
{
	/** @var int */
	const TYPE = 1;

	/** @var string */
	const INDEX = 'research';

	/** @var string */
	const CLASS_ELEMENT = 'U1_Research_Element';

	/** @var string */
	const CLASS_EXCEPTION = 'U1_Research_Exception';


	/** @var array = array() : {id_p => int} Temps de construction réduit pour les éléments*/
	protected $reduceTime = array();

	/** @var array = array() : {id_g => cost, ...}*/
	protected $cost = array();


	/** @var array : {id_planet => {elements => {}, groupelements => {}, unblock => {}, lists => {}, structurelists => {}}, ... }*/
	private $_storagePlanet = array();


	/** @var array : {id_user => #field, ... }*/
	static private $_instance = array();


	/** @var array : contient les messages d'erreur*/
	public $errorMessages = array(
		0 => 'Erreur recherche',
		1 => 'L\'élément doit être du type de ',
		2 => 'Recherche non terminé',
		3 => 'Recherche non débloqué',
		4 => 'Impossible de construire la recherche',
		5 => 'Impossible d\'arrêter la construction de la recherche',
		6 => 'Impossible de terminé la recherche',
		7 => 'Recherche déjà en construction',
		8 => 'Recherche inexistante',
		9 => 'Liste de la recherche inexistante',
		//100 -> SW_Structure_Observer_Single
		200 => 'Une recherche est déjà en construction',
		201 => 'La planète n\'existe pas'
	);


	/**
	 * @param SW_Player $player
	 * @exception U1_Research_Exception U1_Research ne peut avoir plusieurs instance pour un même joueur.
	 */
	public function __construct(SW_Player $player)
	{
		if (isset(self::$_instance[$player->getId()]))
			throw new U1_Research_Exception('U1_Research ne peut avoir plusieurs instance pour un même joueur.');
		self::$_instance[$player->getId()] = $this;

		parent::__construct($player, 0, U1_Research_Dependency::factory(self::INDEX), U1_Research_DAO::getFactory(), self::CLASS_ELEMENT, self::CLASS_EXCEPTION);
	}

	/**
	 * Retourne une exception. Toutes les exceptions lancée par U1_Research sont retourné par cette méthode.
	 * @param int $code : code du message dans $errorMessages
	 * @param Exception $previous = null
	 * @return U1_Research_Exception
	 */
	public function getException($code, $previous = null)
	{
		return new U1_Research_Exception(
			$this->errorMessages[isset($this->errorMessages[$code]) ? $code : 0],
			$code,
			$previous
		);
	}

	/**
	 * Ajoute un élement, l'enregister et retourne un objet de la structure créé
	 * @param int $group
	 * @param int $level = 0
	 * @return SW_Structure_Element
	 */
	public function createElement($group, $level=0)
	{
		$element = $this->dao->createElement($this->player->getId(), $group, $level);
		$this->add($element);
		return $element;
	}

	/**
	 * @param SW_Player $player
	 */
	static public function exitInstance(SW_Player $player)
	{
		return isset(self::$_instance[$player->getId()]);
	}

	/**
	 * @param SW_Player $player
	 * @return #field|null
	 */
	static public function getInstance(SW_Player $player)
	{
		return isset(self::$_instance[$player->getId()]) ? self::$_instance[$player->getId()] : null;
	}

	/**
	 * @param SW_Structure_Element $element
	 * @return #field
	 * @exception U1_Research_Exception : Recherche inexistante
	 */
	protected function initElement(SW_Structure_Element $element)
	{
		$this->initElementForTheDuration($element, 1);
		return $this;
	}

	/**
	 * Initialise les attributs de l'élement pour qu'ensuite il soit construit
	 * @param U1_Research_Element $element
	 * @param int $duration
	 * @exception U1_Research_Exception : Recherche inexistante
	 */
	public function initElementForTheDuration(U1_Research_Element $element, $duration)
	{
		$this->_initElementForTheDuration($element->setPositionPlanet($this->idPlanet), $duration);
	}

	/**
	 * Initialise les attributs de l'élement pour qu'ensuite il soit construit
	 * @param U1_Research_Element $element
	 * @param int $duration
	 * @exception U1_Research_Exception : Recherche inexistante
	 */
	protected function _initElementForTheDuration(U1_Research_Element $element, $duration)
	{
		if (!self::runtimeElement($element->getGroup()))
			throw new $this->classException($this->errorMessages[8], 8);

		$race = $this->player->getRace();
		$element->setDuration($duration)
			->setGainForTheDuration($this->calculateGain($element, $race))
			->setPercentByTheEndOfTheDuration(self::calculatePercent($element))
			->setPrice($this->calculatePrice($element, $race));
	}

	/**
	 * @return bool
	 */
	static public function runtimeElement($group)
	{
		return (0 <= $group && $group <= 12);
	}

	/**
	 * Calcule le gain de la recherche pour la durée derterminée dans l'élément
	 * @param U1_Research_Element $e
	 * @param int $race
	 * @return int
	 */
	protected function calculateGain(U1_Research_Element $e, $race)
	{
		//raison, différe selon les recherches et la race (diminue pour les grandes recherches (q>1))
		$coef = (U1_Race::getIndex('ashen') === $race ?	.006762	//105%
			: (U1_Race::getIndex('werlingz')=== $race ?	.005796 //90%
			: (U1_Race::getIndex('eldarks') === $race ?	.007084	//110%
			: .00644))
		);

		return (int)($coef * $e->getDuration() * (1 + $this->reduceTime[$this->idPlanet]));
	}

	/**
	 * Calcule le prix de la recherche pour la durée déterminée dans l'élément
	 * @param U1_Research_Element $e
	 * @param int $race
	 * @return array
	 */
	protected function calculatePrice(U1_Research_Element $e, $race)
	{
		$G = $e->getGain();
		$g = $e->getGainForTheDuration();
		/*1+2+3+...+($g+$G) - 1+2+3+...+$G
		(n²+n)/2 => (($G+$g)² + $g + $G - ($G² + $G)) / 2
		*/
		$coef = (U1_Race::getIndex('werlingz') === $race ? 1.85 : 1.5) * $g * (0.5 + $g/2 + $G) / (1 + $this->reduceTime[$this->idPlanet]);

		return array((int)($coef), (int)($coef));
	}

	/**
	 * Calcule le percent de la recherche pour le gain de fin de construction déterminé dans l'élément
	 * @param U1_Research_Element $e
	 * @return int
	 */
	static public function calculatePercent(U1_Research_Element $e)
	{
		return (int)sqrt($e->getGain() + $e->getGainForTheDuration());
	}

	/**
	 * Calcule reduceTime, la durée à diviser à la recherche
	 * @return float
	 */
	public function calculateReduceTime()
	{
		$building = $this->getSiblingInstance(U1_Building::INDEX);
		return -0.9
			+ pow($this->getLevelMaxGroup(10)/10, 1.2) * 5
			+ pow($building->getLevelSumGroup(0)*2, 2.3) * 1.8
			+ pow($building->getLevelSumGroup(2), 1.3);
	}

	/**
	 * @return void
	 */
	protected function _init()
	{
		$this->reduceTime[$this->idPlanet] = $this->calculateReduceTime();
		$this->attachOnGroup(10, $this)
			->getSiblingInstance(U1_Building::INDEX)
				->attachOnGroup(0, $this)
				->attachOnGroup(2, $this);

		parent::_init();
	}

	/**
	 * Est lancer quand un obervateur et déclencher
	 * @param SW_I_Element_Subject $subject
	 */
	protected function _update(SW_I_Element_Subject $subject)
	{
		$g = $subject->getGroup();
		$race = $this->player->getRace();
		if ($subject instanceof U1_Building_Element && (0 === $g || 2 === $g))
		{
			$this->reduceTime[$this->idPlanet] = $this->calculateReduceTime();

			foreach ($this->elements as $element)
				$this->_initElementForTheDuration($element->setPositionPlanet($this->idPlanet), 1);
		}
		elseif ($subject instanceof U1_Research_Element)
		{
			if (10 === $g)
			{
				//change le réduce time et initialise les éléments pour toutes les planètes
				foreach ($this->reduceTime as $id_p => &$r)
				{
					$r = $this->calculateReduceTime();
					foreach ($this->elements as $element)
						$this->_initElementForTheDuration($element->setPositionPlanet($id_p), 1);
				}
				//sélectionne la bonne planète pour les éléments
				foreach ($this->elements as $element)
					$element->setPositionPlanet($this->idPlanet);
			}
			else
			{
				$this->_initElementForTheDuration($element->setPositionPlanet($id_p), 1);
			}
		}
	}

	/**
	 * Retourne true lorsque la structure peut arrêter des éléments. Ne vérifie pas qu'un élément puisse être arrêter (rôle de elementCanDown).
	 * @param int $timestamp
	 * @return bool|int : si int alors code d'erreur dans $errorMessages (true|200)
	 */
	public function structureCanBuild($timestamp)
	{
		//une recherche est déjà en cours
		return empty($this->lists) ? parent::structureCanBuild($timestamp) : 200;
	}

	/**
	 * lance la construction immédiatement sans faire les vérification de canBuild
	 * @param SW_Structure_Element $element
	 * @param int $timestamp
	 * @param int $count : nombre d'heure pour faire évoluer la structure
	 * @return SW_Structure_List
	 * @exception SW_Resource_Exception
	 */
	protected function _build(SW_Structure_Element $element, $timestamp, $count)
	{
		$element->setPositionPlanet($this->idPlanet);
		if ($element->getDuration() != 3600 * $count)
			$this->_initElementForTheDuration($element, $count);

		return parent::_build($element, $timestamp, $element->getGainForTheDuration());
	}

	/**
	 * Retourne true lorsque la structure peut arrêter des éléments. Ne vérifie pas qu'un élément puisse être arrêter (rôle de elementCanDown).
	 * @param int $timestamp
	 * @return bool|int : si int alors code d'erreur dans $errorMessages (5)
	 */
	public function structureCanDown($timestamp)
	{
		return 5;
	}

	/**
	 * Change la planète interne pour le traitment et l'ajoute au planète si inexistant
	 * @param int $id_planet
	 * @return #field
	 * @exception U1_Research_Exception : code 201
	 */
	public function setPositionPlanet($id_planet)
	{
		$id_planet = (int)$id_planet;
		if ($this->idPlanet !== $id_planet)
		{
			$planets = $this->player[SW_Planet::INDEX];
			if (!$planets->exists($id_planet))
				throw new $this->classException($this->errorMessages[201], 201);

			if (!$planets->existsInstance(self::INDEX, $id_planet))
				$planets->addStorageElement($this, self::INDEX, $id_planet);

			if (!isset($this->_storagePlanet[$id_planet]))
			{
				$this->_storagePlanet[$id_planet] = array(
					'unblock' => array(),
					'lists' => array(),
					'structurelists' => array(),
					'attach_element' => array(),
					'isInit' => array()
				);
				$this->reduceTime[$id_planet] = null;
				$this->player[SW_Planet::INDEX]->addStorageElement($this, constant(get_class($this).'::INDEX'), $id_planet);
			}
			$storage = &$this->_storagePlanet[$id_planet];

			$this->idPlanet			= $id_planet;
			$this->isInit			= &$storage['isInit'];
			$this->unblock			= &$storage['unblock'];
			$this->lists			= &$storage['lists'];
			$this->structureLists	= &$storage['structurelists'];
			$this->attachElements	= &$storage['attach_element'];

			foreach ($this->elements as $element)
				$element->setPositionPlanet($id_planet);
		}

		return $this;
	}

	/**
	 * Fini la construction de la structure demander
	 * @param SW_Structure_List $list
	 * @param int $timestamp
	 * @exception U1_Building_Exception $this->classException
	 */
	protected function _finish(SW_Structure_List $list, $timestamp)
	{
		$this->player->addFinishElement($this, $list);
		parent::_finish($list, $timestamp);
	}

	public function __toString()
	{
		return self::INDEX;
	}
}